#include "Shader.hpp"
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <GLFW/glfw3.h>

/*
	The code below is somewhat based on https://github.com/heisters/ofxShader
*/


Shader::Shader(std::string const & vertex_file_path, std::string const & geometry_file_path, std::string const & fragment_file_path) :
	programId_(0)
{
	GLuint VertexShaderID = compileShader(vertex_file_path, GL_VERTEX_SHADER);
//	GLuint GeometryShaderID = compileShader(geometry_file_path, GL_GEOMETRY_SHADER); 
	GLuint FragmentShaderID = compileShader(fragment_file_path,GL_FRAGMENT_SHADER);

	// Link the program
	std::cout << "Linking program\n";
	programId_ = glCreateProgram();
	glAttachShader(programId_, VertexShaderID);
//	glAttachShader(programId_, GeometryShaderID); 
	glAttachShader(programId_, FragmentShaderID); 
	glLinkProgram(programId_);

		
	GLint Result = GL_FALSE;
	int InfoLogLength;

	// Check the program
	glGetProgramiv(programId_, GL_LINK_STATUS, &Result);
	glGetProgramiv(programId_, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if ( InfoLogLength > 0 ){
		std::vector<char> ProgramErrorMessage(InfoLogLength+1);
		glGetProgramInfoLog(programId_, InfoLogLength, NULL, &ProgramErrorMessage[0]);
		printf("%s\n", &ProgramErrorMessage[0]);
	}

	glDeleteShader(VertexShaderID);
//	glDeleteShader(GeometryShaderID); 
	glDeleteShader(FragmentShaderID);
}


Shader::Shader(std::string const & vertex_file_path, std::string const & tessCtrl_file_path, std::string const & tessEval_file_path, std::string const & geometry_file_path, std::string const & fragment_file_path)
	: programId_(0)
{


	GLuint VertexShaderID = compileShader(vertex_file_path, GL_VERTEX_SHADER);
	GLuint TessCtrlShaderID = compileShader(tessCtrl_file_path,GL_TESS_CONTROL_SHADER);
	GLuint TessEvalShaderID = compileShader(tessEval_file_path,GL_TESS_EVALUATION_SHADER); 
	GLuint GeometryShaderID = compileShader(geometry_file_path,GL_GEOMETRY_SHADER); 
	GLuint FragmentShaderID = compileShader(fragment_file_path,GL_FRAGMENT_SHADER);


	// Link the program
	std::cout << "Linking program\n";
	programId_ = glCreateProgram();
	glAttachShader(programId_, VertexShaderID);
	glAttachShader(programId_, TessCtrlShaderID);
	glAttachShader(programId_, TessEvalShaderID); 
	glAttachShader(programId_, GeometryShaderID); 
	glAttachShader(programId_, FragmentShaderID); 
	glLinkProgram(programId_);

		
	GLint Result = GL_FALSE;
	int InfoLogLength;

	// Check the program
	glGetProgramiv(programId_, GL_LINK_STATUS, &Result);
	glGetProgramiv(programId_, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if ( InfoLogLength > 0 ){
		std::vector<char> ProgramErrorMessage(InfoLogLength+1);
		glGetProgramInfoLog(programId_, InfoLogLength, NULL, &ProgramErrorMessage[0]);
		printf("%s\n", &ProgramErrorMessage[0]);
	}

	glDeleteShader(VertexShaderID);
	glDeleteShader(TessCtrlShaderID);
	glDeleteShader(TessEvalShaderID);
	glDeleteShader(GeometryShaderID); 
	glDeleteShader(FragmentShaderID);
}


GLuint Shader::compileShader(std::string const & filename, GLenum shaderType){
	// Read the Vertex Shader code from the file
	std::string VertexShaderCode;
	std::ifstream VertexShaderStream(filename, std::ios::in);
	VertexShaderStream.exceptions(std::ifstream::badbit);
	if (!VertexShaderStream.is_open()){
		throw std::ifstream::failure("File " + filename + " not found");
	}
	std::string Line = "";
	while(!VertexShaderStream.eof() && getline(VertexShaderStream, Line)) {
//		std::cout << Line << std::endl;
		VertexShaderCode += "\n" + Line;
	}
	VertexShaderStream.close();

	GLuint shaderID = glCreateShader(shaderType);
	std::cout << "Compiling shader : " << filename << std::endl;
	char const * VertexSourcePointer = VertexShaderCode.c_str();
	glShaderSource(shaderID, 1, &VertexSourcePointer , NULL);
	glCompileShader(shaderID);


	
	GLint Result = GL_FALSE;
	int InfoLogLength;

		// Check Vertex Shader
	glGetShaderiv(shaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if (Result == GL_FALSE) {
		std::cerr << "Error compiling shader: " << filename << std::endl;
	}
	if ( InfoLogLength > 0 ){
		std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
		glGetShaderInfoLog(shaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
		std::cout << &VertexShaderErrorMessage[0] << std::endl;
	}

	if (Result == GL_FALSE) {
		glDeleteShader(shaderID);
	}

	return shaderID;

}


Shader::~Shader(void)
{
	if (programId_) {
		glDeleteProgram(programId_);
	}
}



void Shader::begin() {

glUseProgram(programId_);
}
void Shader::end() {

glUseProgram(0);
}

void Shader::setUniform(const char* name, int v1) {

glUniform1i(getLoc(name), v1);
}
void Shader::setUniform(const char* name, int v1, int v2) {

glUniform2i(getLoc(name), v1, v2);
}
void Shader::setUniform(const char* name, int v1, int v2, int v3) {

glUniform3i(getLoc(name), v1, v2, v3);
}
void Shader::setUniform(const char* name, int v1, int v2, int v3, int v4) {

glUniform4i(getLoc(name), v1, v2, v3, v4);
}
void Shader::setUniform(const char* name, float v1) {

glUniform1f(getLoc(name), v1);
}
void Shader::setUniform(const char* name, float v1, float v2) {

glUniform2f(getLoc(name), v1, v2);
}
void Shader::setUniform(const char* name, float v1, float v2, float v3) {

glUniform3f(getLoc(name), v1, v2, v3);
}
void Shader::setUniform(const char* name, float v1, float v2, float v3, float v4) {

glUniform4f(getLoc(name), v1, v2, v3, v4);
}
void Shader::setUniform1v(const char* name, int* v, int count) {

glUniform1iv(getLoc(name), count, (const GLint*)&v);
}
void Shader::setUniform2v(const char* name, int* v, int count) {

glUniform2iv(getLoc(name), count, (const GLint*)&v);
}
void Shader::setUniform3v(const char* name, int* v, int count) {

glUniform3iv(getLoc(name), count, (const GLint*)&v);
}
void Shader::setUniform4v(const char* name, int* v, int count) {

glUniform4iv(getLoc(name), count, (const GLint*)&v);
}
void Shader::setUniform1v(const char* name, float* v, int count) {

glUniform1fv(getLoc(name), count, v);
}
void Shader::setUniform2v(const char* name, float* v, int count) {

glUniform2fv(getLoc(name), count, v);
}
void Shader::setUniform3v(const char* name, float* v, int count) {

glUniform3fv(getLoc(name), count, v);
}



void Shader::setUniformMatrix4v(const char* name, float* v, int count, bool transpose) {
	GLboolean glTranspose = transpose ? GL_TRUE : GL_FALSE;
	glUniformMatrix4fv(getLoc(name), count, glTranspose, v);
}
void Shader::setUniform4v(const char* name, float* v, int count) {

glUniform4fv(getLoc(name), count, v);
}
void Shader::setAttribute(const char* name, short v1) {

glVertexAttrib1s(getAttributeLocation(name), v1);
}
void Shader::setAttribute(const char* name, short v1, short v2) {

glVertexAttrib2s(getAttributeLocation(name), v1, v2);
}
void Shader::setAttribute(const char* name, short v1, short v2, short v3) {

glVertexAttrib3s(getAttributeLocation(name), v1, v2, v3);
}
void Shader::setAttribute(const char* name, short v1, short v2, short v3, short v4) {

glVertexAttrib4s(getAttributeLocation(name), v1, v2, v3, v4);
}
void Shader::setAttribute(const char* name, float v1) {

glVertexAttrib1f(getAttributeLocation(name), v1);
}
void Shader::setAttribute(const char* name, float v1, float v2) {

glVertexAttrib2f(getAttributeLocation(name), v1, v2);
}
void Shader::setAttribute(const char* name, float v1, float v2, float v3) {

glVertexAttrib3f(getAttributeLocation(name), v1, v2, v3);
}
void Shader::setAttribute(const char* name, float v1, float v2, float v3, float v4) {

glVertexAttrib4f(getAttributeLocation(name), v1, v2, v3, v4);
}
void Shader::setAttribute(const char* name, double v1) {

glVertexAttrib1d(getAttributeLocation(name), v1);
}
void Shader::setAttribute(const char* name, double v1, double v2) {

glVertexAttrib2d(getAttributeLocation(name), v1, v2);
}
void Shader::setAttribute(const char* name, double v1, double v2, double v3) {

glVertexAttrib3d(getAttributeLocation(name), v1, v2, v3);
}
void Shader::setAttribute(const char* name, double v1, double v2, double v3, double v4) {

glVertexAttrib4d(getAttributeLocation(name), v1, v2, v3, v4);
}
void Shader::setAttributeLocation(const char* name, int index) {
glBindAttribLocation(programId_, index, name);
}
int Shader::getAttributeLocation(const char* name) {
return glGetAttribLocation(programId_, name);
}
inline GLint Shader::getLoc(const char* name) {
return glGetUniformLocation(programId_, name);
}
